﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Base
{
    /// <summary>
    /// 进程扩展方法
    /// </summary>
    public partial class ProcessEx
    {
        /// <summary>
        /// 单进程检测[创建进程互斥对象成功返回true;否则返回false]
        /// </summary>
        /// <param name="rangeFlag">单进程检测范围[true:所有用户;false:仅当前用户]</param>
        /// <param name="mutexName">互斥变量名称</param>
        /// <param name="mutex">互斥所对象</param>
        /// <returns>创建进程互斥对象成功返回true;否则返回false</returns>
        public static bool TryCreataSingleProcessMutex(bool rangeFlag, string mutexName, out Mutex mutex)
        {
            mutex = null;
            if (string.IsNullOrEmpty(mutexName))
            {
                mutexName = Assembly.GetEntryAssembly().GetCustomAttributes(true).Where(t => t is GuidAttribute).Cast<GuidAttribute>().Select(t => t.Value).FirstOrDefault();
                if (string.IsNullOrWhiteSpace(mutexName))
                {
                    //mutexName = AppDomain.CurrentDomain.SetupInformation.ApplicationName;
                    mutexName = Path.GetFileNameWithoutExtension(Process.GetCurrentProcess().MainModule.FileName);
                }
            }

            if (rangeFlag)
            {
                mutexName = @"Global\" + mutexName;
            }

            bool createNew;
            try
            {
#if NET4_0
                try
                {
                    Mutex oldMutex = Mutex.OpenExisting(mutexName);
                    return false;
                }
                catch(UnauthorizedAccessException)
                {
                    //The named mutex exists, but the user does not have the security access required to use it.
                    return false;
                }
                catch (WaitHandleCannotBeOpenedException)
                {
                    //The named mutex does not exist.
                }
#else
                Mutex oldMutex;
                if (Mutex.TryOpenExisting(mutexName, out oldMutex))
                {
                    return false;
                }
#endif

                mutex = new Mutex(true, mutexName, out createNew);
                return createNew;
            }
            catch
            {
                createNew = false;
            }

            return createNew;
        }

        /// <summary>
        /// 执行命令行程序并返回输出结果字符串
        /// </summary>
        /// <param name="commandLine">要执行的命令行</param>
        /// <returns>执行输出结果字符串</returns>
        public static string ExecuteCommand(string commandLine)
        {
            var process = new System.Diagnostics.Process
            {
                StartInfo = new System.Diagnostics.ProcessStartInfo()
                {
                    //FileName = appPath,
                    Arguments = string.Empty,
                    RedirectStandardInput = true,
                    RedirectStandardOutput = true,
                    RedirectStandardError = true,
                    UseShellExecute = false,
                    CreateNoWindow = true,
                    WindowStyle = ProcessWindowStyle.Hidden,
                },
                //EnableRaisingEvents = true
            };

            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Win32NT:
                    process.StartInfo.FileName = @"cmd.exe";
                    break;
                case PlatformID.Unix:
                    process.StartInfo.FileName = @"/bin/bash";
                    break;
                case PlatformID.MacOSX:
                default:
                    throw new NotImplementedException($"未实现的OS平台\"{Environment.OSVersion.Platform.ToString()}\"");
            }

            //pro.Exited += (s, e) =>
            //{
            //    autoResetEvent.Set();
            //};

            try
            {
                process.Start();
                process.StandardInput.WriteLine(commandLine);
                //process.StandardInput.WriteLine("cls");
                //process.StandardInput.WriteLine("exit");
                process.StandardInput.Close();
                string resultText = process.StandardOutput.ReadToEnd();
                process.StandardOutput.Close();
                process.WaitForExit();
                process.Dispose();
                return resultText;
            }
            finally
            {
                process.Dispose();
            }
        }


        /// <summary>
        /// 验证进程ID是否有效[有效返回true;无效返回false]
        /// </summary>
        /// <param name="id">进程ID</param>
        /// <returns>有效返回true;无效返回false</returns>
        public static bool ValidaProcessId(int id)
        {
            return id > 0;
        }

        /// <summary>
        /// 根据程序路径查找进程
        /// </summary>
        /// <param name="appExeFilePath">根据程序路</param>
        /// <returns>进程列表</returns>
        public static List<Process> FindProcessByFilePath(string appExeFilePath)
        {
            var resultPros = new List<Process>();
            Process[] pros = Process.GetProcesses();
            foreach (var pro in pros)
            {
                try
                {
                    if (string.Equals(appExeFilePath, pro.MainModule.FileName, StringComparison.OrdinalIgnoreCase))
                    {
                        resultPros.Add(pro);
                    }
                }
                catch
                { }
            }

            return resultPros;
        }

        private static void PrimitiveKill(Process process, int hostProcessId)
        {
            try
            {
                process.Refresh();
                if (process == null || process.HasExited)
                {
                    return;
                }

                if (process.Id == hostProcessId)
                {
                    return;
                }

                process.Kill();
            }
            catch
            { }
        }

        /// <summary>
        /// 通过指定应用程序的名称和一组命令行参数来启动一个进程资源，并将该资源与新的 System.Diagnostics.Process 组件相关联
        /// </summary>
        /// <param name="fileName">要在该进程中运行的应用程序文件的名称</param>
        /// <param name="arguments">启动该进程时传递的命令行实参</param>
        /// <returns>与该进程关联的新的 System.Diagnostics.Process 组件；如果没有启动进程资源（例如，如果重用了现有进程），则为 null</returns>
        public static Process Start(string fileName, string arguments)
        {
            var startInfo = new ProcessStartInfo();
            startInfo.UseShellExecute = true;
            startInfo.FileName = fileName;
            startInfo.Arguments = arguments;
            return Process.Start(startInfo);
        }






        /// <summary>
        /// 获取网络端口关联的进程ID列表,未获取到返回null
        /// </summary>
        /// <param name="port">网络端口</param>
        /// <param name="flag">方向标识[true:仅本地;false:仅远方;null:全部]</param>
        /// <returns>进程ID</returns>
        public static List<int> GetProcessIdByPort(int port, bool? flag = null)
        {
            switch (Environment.OSVersion.Platform)
            {
                case PlatformID.Win32NT:
                    return GetProcessIdByPortForWindows(port, flag);
                case PlatformID.Unix:
                    return GetProcessIdByPortForLinux(port, flag);
                case PlatformID.MacOSX:
                default:
                    throw new NotImplementedException($"未实现的OS平台\"{Environment.OSVersion.Platform.ToString()}\"");
            }
        }

        private static List<int> GetProcessIdByPortForLinux(int port, bool? flag)
        {
            /************************************************************************************
             * [root@localhost ~]# netstat -tunp
             * Active Internet connections (w/o servers)
             * Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
             * tcp        0      1 192.168.0.105:51210     34.117.65.55:443        SYN_SENT    6046/firefox        
             * tcp        0      0 127.0.0.1:33551         127.0.0.1:54798         ESTABLISHED 32988/wpsoffice     
             * tcp        0      0 127.0.0.1:53860         127.0.0.1:35571         ESTABLISHED 32988/wpsoffice     
             * tcp        0      0 127.0.0.1:35571         127.0.0.1:53860         ESTABLISHED 32988/wpsoffice     
             * tcp        0     32 192.168.0.105:55468     20.42.65.85:443         LAST_ACK    -                   
             * tcp        0      0 192.168.0.105:52048     34.117.65.55:443        ESTABLISHED 6046/firefox        
             * tcp        0      0 127.0.0.1:54798         127.0.0.1:33551         ESTABLISHED 32988/wpsoffice     
             * tcp        0      0 192.168.0.105:60256     52.236.186.218:443      ESTABLISHED 51170/code          
             * tcp        0     32 192.168.0.105:55472     20.42.65.85:443         LAST_ACK    -                   
             * [root@localhost ~]# 
             * 
             ************************************************************************************/

            string text = ProcessEx.ExecuteCommand($"netstat -tunp | grep {port}");
#if CORE
            string[] lineArr = text.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
#else
            string[] lineArr = text.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
#endif

            string[] lineSplits = new string[] { " " };
            string[] lineStrArr;
            string portStr = port.ToString();
            List<int> pidList = null;
            string pidStr;
            int spaceChIndex, rightSlashIndex, pid;

            for (int i = 0; i < lineArr.Length; i++)
            {
                if (lineArr[i].StartsWith("tcp", StringComparison.OrdinalIgnoreCase) || lineArr[i].StartsWith("udp", StringComparison.OrdinalIgnoreCase))
                {
                    if (flag.HasValue)
                    {
                        lineStrArr = lineArr[i].Split(lineSplits, StringSplitOptions.RemoveEmptyEntries);
                        if (lineStrArr == null || lineStrArr.Length < 7)
                        {
                            continue;
                        }

                        if (flag.Value)
                        {
                            if (!lineStrArr[3].EndsWith(portStr))
                            {
                                continue;
                            }
                        }
                        else
                        {
                            if (!lineStrArr[4].EndsWith(portStr))
                            {
                                continue;
                            }
                        }
                    }

                    spaceChIndex = lineArr[i].LastIndexOf(' ');
                    if (spaceChIndex >= 0)
                    {
                        rightSlashIndex = lineArr[i].LastIndexOf(PathEx.RightSlash);
                        if (rightSlashIndex > 0)
                        {
                            pidStr = lineArr[i].Substring(spaceChIndex, rightSlashIndex - spaceChIndex).Trim();
                            if (int.TryParse(pidStr, out pid))
                            {
                                if (pidList == null)
                                {
                                    pidList = new List<int>();
                                }

                                if (!pidList.Contains(pid))
                                {
                                    pidList.Add(pid);
                                }
                            }
                        }
                    }
                }
            }

            return pidList;
        }

        private static List<int> GetProcessIdByPortForWindows(int port, bool? flag)
        {
            /************************************************************************************
             * Microsoft Windows [版本 10.0.19044.1288]
             * (c) Microsoft Corporation。保留所有权利。
             * 
             * D:\SelfCode\UtilZ\UtilZ.DotnetEx\Test\ConAppCore\bin\Debug\net7.0>netstat -ano|findstr 9057
             *   TCP    0.0.0.0:9057           0.0.0.0:0              LISTENING       3696
             *   TCP    192.168.10.96:9057     192.168.10.96:63859    ESTABLISHED     3696
             *   TCP    192.168.10.96:63859    192.168.10.96:9057     ESTABLISHED     18732
             * 
             * D:\SelfCode\UtilZ\UtilZ.DotnetEx\Test\ConAppCore\bin\Debug\net7.0>
             * 
             * 
             * Microsoft Windows [版本 10.0.19044.1288]
             * (c) Microsoft Corporation。保留所有权利。
             * 
             * D:\SelfCode\UtilZ\UtilZ.DotnetEx\Test\ConAppCore\bin\Debug\net7.0>netstat -ano|findstr 5401
             *   UDP    0.0.0.0:5401           *:*                                    11772
             * 
             * D:\SelfCode\UtilZ\UtilZ.DotnetEx\Test\ConAppCore\bin\Debug\net7.0>
             * 
             ************************************************************************************/

            string text = ProcessEx.ExecuteCommand($"netstat -ano|findstr {port}");
#if CORE
            string[] lineArr = text.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
#else
            string[] lineArr = text.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries);
#endif
            string[] lineSplits = new string[] { " " };
            string[] lineStrArr;
            string portStr = port.ToString();
            List<int> pidList = null;
            int index, pid;

            for (int i = 0; i < lineArr.Length; i++)
            {
#if CORE
                if (lineArr[i].Contains("TCP", StringComparison.OrdinalIgnoreCase) || lineArr[i].Contains("UDP", StringComparison.OrdinalIgnoreCase))
#else
                if (lineArr[i].ToUpper().Contains("TCP") || lineArr[i].ToUpper().Contains("UDP"))
#endif
                {
                    if (flag.HasValue)
                    {
                        lineStrArr = lineArr[i].Split(lineSplits, StringSplitOptions.RemoveEmptyEntries);
                        if (lineStrArr == null || lineStrArr.Length < 4)
                        {
                            continue;
                        }

                        if (flag.Value)
                        {
                            if (!lineStrArr[1].EndsWith(portStr))
                            {
                                continue;
                            }
                        }
                        else
                        {
                            if (!lineStrArr[2].EndsWith(portStr))
                            {
                                continue;
                            }
                        }
                    }

                    index = lineArr[i].LastIndexOf(' ');
                    if (index >= 0)
                    {
                        if (int.TryParse(lineArr[i].Substring(index).Trim(), out pid))
                        {
                            if (pidList == null)
                            {
                                pidList = new List<int>();
                            }

                            if (!pidList.Contains(pid))
                            {
                                pidList.Add(pid);
                            }
                        }
                    }
                }
            }

            return pidList;
        }





        /// <summary>
        /// 构造函数
        /// </summary>
        public ProcessEx()
        {

        }




        private Process _process = null;
        private long _lastTotalProcessorTimestamp = 0;
        private TimeSpan _lastTotalProcessorTime = TimeSpan.Zero;
        private ProcessResourseUsageInfo? _lastProcessResourseUsageInfo = null;

        /// <summary>
        /// 与指定进程关联
        /// </summary>
        /// <param name="process">要关联的进程</param>
        public void AssociateProcess(Process process)
        {
            try
            {
                this._lastProcessResourseUsageInfo = null;
                if (process == null)
                {
                    this._lastTotalProcessorTimestamp = 0;
                    this._process = null;
                    return;
                }

                this._lastTotalProcessorTimestamp = TimeEx.GetTimestamp(process.StartTime);
                this._process = process;
            }
            catch (Exception ex)
            {
                ZLoger.Warn(ex);
                this._lastTotalProcessorTimestamp = 0;
            }

            this._lastTotalProcessorTime = TimeSpan.Zero;
        }

        /// <summary>
        /// 获取进程资源使用信息
        /// </summary>
        /// <returns>进程资源使用信息</returns>
        public ProcessResourseUsageInfo GetProcessResourseUsageInfo()
        {
            var process = this._process;
            if (process == null || process.HasExited)
            {
                return new ProcessResourseUsageInfo(0, 0, 0);
            }

            ProcessResourseUsageInfo? processResourseUsageInfo = null;
            try
            {
                if (this._lastTotalProcessorTime == TimeSpan.Zero)
                {
                    this._lastTotalProcessorTime = process.TotalProcessorTime;
                    processResourseUsageInfo = new ProcessResourseUsageInfo(0, 0, 0);
                }
                else
                {
                    var lastTotalProcessorTime = this._lastTotalProcessorTime;
                    this._lastTotalProcessorTime = process.TotalProcessorTime;

                    var lastTotalProcessorTimestamp = this._lastTotalProcessorTimestamp;
                    var currentTotalProcessorTimestamp = TimeEx.GetTimestamp();
                    this._lastTotalProcessorTimestamp = currentTotalProcessorTimestamp;

                    TimeSpan cpuUsedMs = this._lastTotalProcessorTime - lastTotalProcessorTime;
                    long totalMsPassed = currentTotalProcessorTimestamp - lastTotalProcessorTimestamp;
                    var cpuUsageTotalRateValue = (cpuUsedMs.TotalMilliseconds / (totalMsPassed * Environment.ProcessorCount)) * 100;
                    int cpuRate;
                    if (cpuUsageTotalRateValue < 0)
                    {
                        cpuRate = 0;
                    }
                    else if (cpuUsageTotalRateValue > 100)
                    {
                        cpuRate = 100;
                    }
                    else
                    {
                        cpuRate = Convert.ToInt32(cpuUsageTotalRateValue);
                    }

                    processResourseUsageInfo = new ProcessResourseUsageInfo(cpuRate, process.WorkingSet64, process.Threads.Count);
                }
            }
            catch (Exception ex)
            {
                ZLoger.Warn(ex);
            }

            if (processResourseUsageInfo.HasValue)
            {
                this._lastProcessResourseUsageInfo = processResourseUsageInfo;
                return processResourseUsageInfo.Value;
            }
            else
            {
                this._lastProcessResourseUsageInfo = null;
                return new ProcessResourseUsageInfo(0, 0, 0);
            }
        }

    }

    /// <summary>
    /// 进程资源使用信息
    /// </summary>
    public struct ProcessResourseUsageInfo
    {
        /// <summary>
        /// CPU使用率
        /// </summary>
        public readonly int CPUUsageRate;

        /// <summary>
        /// 内存占用大小
        /// </summary>
        public readonly long MemoryUsage;

        /// <summary>
        /// 线程数
        /// </summary>
        public readonly int ThreadNum;


        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="cpuUsageRate">CPU使用率</param>
        /// <param name="memoryUsage">内存占用大小</param>
        /// <param name="threadNum">线程数</param>
        public ProcessResourseUsageInfo(int cpuUsageRate, long memoryUsage, int threadNum)
        {
            this.CPUUsageRate = cpuUsageRate;
            this.MemoryUsage = memoryUsage;
            this.ThreadNum = threadNum;
        }

    }
}
